﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Xml;
using System.Xml.Schema;
using System.IO;
using WiimoteLib;
using System.Windows.Forms;

namespace IndoorPositionFinder
{    
    public class XmlProcessor
    {
        private String xmlPath;
        private String schemaPath;
        private bool status;
        private bool verbose;
        /// <summary>
        /// Initialize an XmlProcessor Object
        /// </summary>
        /// <param name="xmlPath">Complete path to the xml file</param>
        /// <param name="schemaPath">Complete path to the Schema use to validate the XML file</param>
        public XmlProcessor(String xmlPath, String schemaPath)
        {
            this.status = true;
            this.xmlPath = xmlPath;
            this.schemaPath = schemaPath;
            this.verbose = true;
        }
        /// <summary>
        /// Initialize an XmlProcessor Object
        /// </summary>
        /// <param name="xmlPath">Complete path to the xml file</param>
        /// <param name="schemaPath">Complete path to the Schema use to validate the XML file</param>
        /// <param name="verbose">Indicates if the class is going to have a verbose behavior</param>
        public XmlProcessor(String xmlPath, String schemaPath, bool verbose)
        {
            this.status = true;
            this.xmlPath = xmlPath;
            this.schemaPath = schemaPath;
            this.verbose = verbose;
        }
        public bool validate()
        {
            // Create the XmlSchemaSet class.
            XmlSchemaSet sc = new XmlSchemaSet();

            // Add the schema to the collection.
            sc.Add(null, this.schemaPath);

            // Set the validation settings.
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ValidationType = ValidationType.Schema;
            settings.Schemas = sc;
            settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);

            // Create the XmlReader object.
            XmlReader reader = XmlReader.Create(xmlPath, settings);

            // Parsing the XML file. 
            while (reader.Read());
            return this.status;
        }
        /// <summary>
        /// Process the XML file to obtain the data associated with a room.
        /// </summary>
        /// <returns>A room object with all the data obtained</returns>
        public Room processRoom(TextBox textBox)
        {
            // If the xml file didn't pass the validation, then return.
            if (!this.status)
            {
                textBox.AppendText("\r\nThe xml file did not pass the validation. XML processing abort");
                return null;
            }
            Room room = new Room();
            XmlReader reader = XmlReader.Create(xmlPath);
            for (reader.Read(); reader.Name != "point"; reader.Read()) ;
            
            // Reading the points that defines the room (Minimum 4 points).
            // Leaves the reader pointer at the begining of a Wiimote element
            while (reader.NodeType == XmlNodeType.Element && reader.Name == "point")
            {
                textBox.AppendText("\r\nReading a point");
                // Extracting and saving a point in a List
                room.Points.Add(this.getXmlPoint(ref reader, textBox));
                textBox.AppendText("\r\nReading a point");
                // moving to the next element
                moveToNextElement(ref reader);
            }
            // Reading the wiimotes from the XML file.
            while (reader.NodeType == XmlNodeType.Element && reader.Name == "wiimote")
            {
                this.getXmlWiimote(ref reader, room.Wiimotes, textBox);
                // moving to the next element
                this.moveToNextElement(ref reader);
            }        
            // Reading a room door. 
            while (reader.NodeType == XmlNodeType.Element && reader.Name == "room_door")
            {
                textBox.AppendText("\r\nReading a room_point");

                for (reader.Read(); reader.NodeType != XmlNodeType.Element; reader.Read()) ;

                // This definition is used from the fact that a door is defined with two points only
                PointF [] doorPoints = new PointF[2];
                             
                int i = 0;
                // Obtaining the points associated with a door
                while(reader.NodeType == XmlNodeType.Element && reader.Name == "point")
                {
                    doorPoints[i++] = this.getXmlPoint(ref reader, textBox);

                    for (reader.Read(); reader.NodeType != XmlNodeType.Element; reader.Read()) ;
                }
                // Adding a door
                room.Doors.Add(new RoomDoor(doorPoints[0], doorPoints[1]));
            }
            // Reading a room object. Now we use a while instruction in order to manage the end of the file
            while (reader.NodeType == XmlNodeType.Element && reader.Name == "room_object")
            {
                textBox.AppendText("\r\nReading a room_object");
                room.RoomObjects.Add(getXmlRoomObject(reader, textBox));

                // Checking if the file was completely read
                if (reader.Name != "room_object" && !moveToNextElement(ref reader))
                {
                    break;
                }
            }
            return room;
        }
        /// <summary>
        /// This method extracts a kind of direction from an XML file.
        /// The convention is the following:
        /// 1: The wiimote is pointing in a positive direction in the X and Y axis.
        /// 2: The wiimote is pointing in a positive direction in the X axis and in a negative direction in the Y axis.
        /// 3: The wiimote is pointing in a negative diretion in the X and Y axis.
        /// 4: The wiimote is pointing in a negative direction in the X axis and in a positive direction in the Y axis. 
        /// All the values are relative to the axis associated with the room.
        /// </summary>
        /// <param name="reader">XmlReader used to read the xml file</param>
        /// <returns>Direction extracted from the XML file in a float type</returns>
        private int getXmlDirection(ref XmlReader reader, TextBox textBox)
        {
            if (this.verbose)
                textBox.AppendText("\r\nReading a direction");
            int direction = 0;
            // reading the content inside the angle element
            for (reader.Read(); reader.NodeType != XmlNodeType.EndElement; reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Text)
                {
                    // Parsing the data considering that decimal separator must be a dot instead the local representation
                    direction = int.Parse(reader.Value);
                }
            }
            if (this.verbose)
                textBox.AppendText("\r\nReading a direction finished");
            return direction;
        }
        /// <summary>
        /// This method extracts an angle from an XML file.
        /// </summary>
        /// <param name="reader">XmlReader used to read the xml file</param>
        /// <returns>Angle extracted from the XML file in a float type</returns>
        private float getXmlAngle(ref XmlReader reader, TextBox textBox)
        {
            float angle = .0f;
            // reading the content inside the angle element
            for (reader.Read(); reader.NodeType != XmlNodeType.EndElement; reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Text)
                {
                    // Parsing the data considering that decimal separator must be a dot instead the local representation
                    angle = float.Parse(reader.Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
                }
            }
            if (this.verbose)
                textBox.AppendText("\r\nAngle: " + angle + " degrees");
            return angle;
        }
        /// <summary>
        /// This method extracts a point from an XML file. It leaves the
        /// reader pointer at the end of the point definition
        /// </summary>
        /// <param name="reader">XmlReader used to read the xml file</param>
        /// <returns>Point extracted from the XML file in a PointF struct</returns>
        private PointF getXmlPoint(ref XmlReader reader, TextBox textBox)
        {
            PointF aux = new PointF();
            // moving to the next element (x_axis)
            moveToNextElement(ref reader);
            // Reading the value from the x_axis
            if (reader.Name == "x_axis")
            {
                for (reader.Read(); reader.NodeType != XmlNodeType.EndElement; reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        // Parsing the data considering that decimal separator must be a dot instead the local representation
                        aux.X = float.Parse(reader.Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
                    }
                }
            }
            for (reader.Read(); reader.Name != "y_axis"; reader.Read()) ;
            if (reader.Name == "y_axis")
            {
                for (reader.Read(); reader.NodeType != XmlNodeType.EndElement; reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        // Parsing the data considering that decimal separator must be a dot instead the local representation
                        aux.Y = float.Parse(reader.Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
                    }
                }
            }
            // This read is used to put the "pointer" in the end of a point element
            for (reader.Read(); reader.Name != "point"; reader.Read()) ;
            if(this.verbose)
                textBox.AppendText("\r\nPoint: {" + aux.X+","+aux.Y+"}");
            
            return aux;
        }
        /// <summary>
        /// Extract a RoomWiimote from the XML file
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="wiimotes">Dictionary where the Wiimote is going to be stored</param>
        private void getXmlWiimote(ref XmlReader reader, Dictionary<String, RoomWiimote> wiimotes, TextBox textBox)
        {
            String wiimoteId = "EOF";
            PointF wiimotePosition = new PointF();
            float wiimoteAngle = .0f;
            int wiimoteDirection = 1;

            if(verbose)
                textBox.AppendText("\r\nReading a Wiimote");

            // moving to the next element (wiimote_id)
            moveToNextElement(ref reader);

            // Reading the id from the wiimote
            if (reader.Name == "wiimote_id")
            {
                for (reader.Read(); reader.NodeType != XmlNodeType.EndElement; reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        wiimoteId = reader.Value;
                    }
                }
            }
            // moving to the next element (point)
            this.moveToNextElement(ref reader);

            if (reader.Name == "point")
            {
                wiimotePosition = this.getXmlPoint(ref reader, textBox);
            }
            // moving to the next element (angle)
            this.moveToNextElement(ref reader);
            if (reader.Name == "angle")
            {
                wiimoteAngle = this.getXmlAngle(ref reader, textBox);
            }
            // moving to the next element (direction)
            this.moveToNextElement(ref reader);
            if (reader.Name == "direction")
            {
                wiimoteDirection = this.getXmlDirection(ref reader, textBox);
            }
            wiimotes.Add(wiimoteId, new RoomWiimote(wiimotePosition, wiimoteAngle, wiimoteDirection));
            if (verbose)
                textBox.AppendText("\r\nReading a Wiimote finished");

        }
        /// <summary>
        /// Extract a Room Object from an XML file
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private RoomObject getXmlRoomObject(XmlReader reader, TextBox textBox)
        {
            if (verbose)
                textBox.AppendText("\r\nReading a Room Object");
            String name = "EOF";
            List<PointF> cornerPoints = new List<PointF>();
            
            // Move to the next element (object_name)
            moveToNextElement(ref reader);

            if (reader.Name == "object_name")
            {
                // If there is more than one name we only save the last
                for (reader.Read(); reader.NodeType != XmlNodeType.EndElement; reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        name = reader.Value;
                    }
                }
            }
            if (this.verbose)
                textBox.AppendText("\r\nName: " + name);
            // Moving to the next element (point)
            this.moveToNextElement(ref reader);
            // Obtaining all the points associated with the objects
            while(reader.Name == "point")
            {
                cornerPoints.Add(getXmlPoint(ref reader, textBox));
                // Moving to the next element
                this.moveToNextElement(ref reader);
            }
            // Moving the pointer to the end of the room_object definition
            if (verbose)
                textBox.AppendText("\r\nReading a Room Object finished");
            return new RoomObject(name, cornerPoints);
            
        }
        /// <summary>
        /// Moving the XmlReader pointer to the next element in the XML file.
        /// </summary>
        /// <param name="reader">Reader which pointer is going to be moved</param>
        /// <returns>Reader status. It returns false when the file it's over</returns>
        private bool moveToNextElement(ref XmlReader reader)
        {
            bool status = true;
            for (status = reader.Read(); reader.NodeType != XmlNodeType.Element && status; status = reader.Read()) ;
            return status;
        }
        /// <summary>
        /// Display any validation errors.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param> 
        private void ValidationCallBack(object sender, ValidationEventArgs e)
        {
            this.status = false;
            Debug.WriteLine("\r\nValidation Error: {0}", e.Message);
        }

    }
}
